﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BackupLib
{
    using System.IO;

    public class BackupWorker
    {
        public string SourceRoot { get; set; }
        private string TargetRoot { get; set; }
        private string LastTargetRoot { get; set; }
        private bool TargetSupportsHardlinks { get; set; }
        
        private bool FullBackup { get; set; }
        private bool Recursive { get; set; }
        
        private IList<string> Excludes { get; set; }
        private IList<string> Includes { get; set; }
        
        private bool ExcludeHidden { get; set; }
        private bool ExcludeSystem { get; set; }
        
        private long? MinSize { get; set; }
        private long? MaxSize { get; set; }

        private DateTime? MinCreated { get; set; }
        private DateTime? MaxCreated { get; set; }
        private DateTime? MinChanged { get; set; }
        private DateTime? MaxChanged { get; set; }


        public void DoBackup()
        {
            Stack<string> stackDir = new Stack<string>();
            // Add initial directory.
            stackDir.Push(SourceRoot);

            // Continue while there are directories to process
            while (stackDir.Count > 0)
            {
                // Get top directory
                string dir = stackDir.Pop();

                try
                {
                    // Add all files at this directory to the result List.
                    foreach (string file in Directory.GetFiles(dir, "*.*"))
                    {
                        try
                        {
                            string relativeFilePath = PathUtil.RelativePathTo(file, SourceRoot);
                            if (MatchesCriteria(relativeFilePath))
                            {
                                FileAttributes attributes = File.GetAttributes(file);

                                if (((attributes & FileAttributes.Archive) != FileAttributes.Archive) && FullBackup)
                                {
                                    //full backup
                                    BackupFile(relativeFilePath, true);
                                }
                                else
                                {
                                    //differenz backup
                                    BackupFile(relativeFilePath);
                                }
                                File.SetAttributes(file, attributes & ~FileAttributes.Archive);
                                //update cache
                                int hash = file.GetHashCode();
                            }
                        }
                        catch (Exception)
                        {
                            throw;
                        }
                    }

                    // Add all directories at this directory.
                    if (Recursive)
                    {
                        foreach (string dn in Directory.GetDirectories(dir))
                        {
                            stackDir.Push(dn);
                        }
                    }
                }
                catch
                {
                    // D
                    // Could not open the directory
                }
            }
        }

        private void BackupFile(string sourceRelative)
        {
            BackupFile(sourceRelative,false);
        }

        private void BackupFile(string sourceRelative, bool noChange)
        {
            string filename = Path.GetFileName(sourceRelative);

            if (noChange && TargetSupportsHardlinks)
            {
                try
                {
                    APIWrapper.CreateHardLink(LastTargetRoot + sourceRelative, TargetRoot + sourceRelative);
                }
                catch (Exception)
                {
                    File.Copy(SourceRoot + sourceRelative, TargetRoot + sourceRelative);
                }
            }
            else
            {
                File.Copy(SourceRoot + sourceRelative, TargetRoot + sourceRelative);
            }
        }

        private bool MatchesCriteria(string file)
        {
            //todo: fi nur bei bedarf
            FileInfo fi = new FileInfo(SourceRoot + file);
           
            foreach (string exclude in Excludes)
            {
                if (file.Contains(exclude)) return false;
            }
            foreach (string include in Includes)
            {
                if (!file.Contains(include)) return false;
            }
            
            if (ExcludeHidden && ((fi.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden))
            {
                return false;
            }
            
            if (ExcludeSystem && ((fi.Attributes & FileAttributes.System) == FileAttributes.System))
            {
                return false;
            }

            if (!ValidateValue(fi.Length, MinSize, MaxSize)) return false;
            if (!ValidateValue(fi.CreationTime, MinCreated, MaxCreated)) return false;
            if (!ValidateValue(fi.LastWriteTime, MinChanged, MaxChanged)) return false;
      
            return true;
        }

        private bool ValidateValue(IComparable o, IComparable min, IComparable max)
        {
            if (min != null)
            {
                if (o.CompareTo(min)<0) return false;
            }
            if (max != null)
            {
                if (o.CompareTo(max)>0) return false;
            }
            return true;
        }

        private void CopyFile(string source, string destination)
        {
            File.Copy(source,destination,true);
        }

        public static List<int> GetFilesRecursive(string b)
        {
            // 1.
            // Store results in the file results list.
            List<int> result = new List<int>();

            // 2.
            // Store a stack of our directories.
            Stack<string> stack = new Stack<string>();

            // 3.
            // Add initial directory.
            stack.Push(b);

            // 4.
            // Continue while there are directories to process
            while (stack.Count > 0)
            {
                // A.
                // Get top directory
                string dir = stack.Pop();

                try
                {
                    // B
                    // Add all files at this directory to the result List.
                    foreach (string file in Directory.GetFiles(dir, "*.*"))
                    {
                        int hash = file.GetHashCode();
                        if (result.Contains(hash)) throw new Exception("");
                        result.Add(hash);
                    }

                    // C
                    // Add all directories at this directory.
                    foreach (string dn in Directory.GetDirectories(dir))
                    {
                        stack.Push(dn);
                    }
                }
                catch
                {
                    // D
                    // Could not open the directory
                }
            }
            return result;
        }

    }
}
